/**
 ** Copyright (c) 2010, GE Healthcare
 ** All rights reserved.
 **
 ** Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 ** following conditions are met:
 ** 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the
 **    following disclaimer.
 ** 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following
 **    disclaimer in the documentation and/or other materials provided with the distribution.
 ** 3. Neither the name of the GE Healthcare nor the names of its contributors may be used to endorse or promote products
 **    derived from this software without specific prior written permission.
 **
 ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 ** INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 ** DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 ** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 ** LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 ** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * This software was created by its contributors for NHINDirect.org for the sole purpose of demonstrating the
 * viability of using Integrating the Healthcare Enterprise (IHE) in accordance with the proposed
 * IHE NHIN Direct Specfication.
 *
 * References:
 * http://nhindirect.org/
 * http://nhindirect.org/NHIN+Direct+Abstract+Model
 * http://nhindirect.org/IHE+NHIN+Direct+Specification
 *
 * Contributors:
 * mailto:/matt.potter@ge.com
 */

package org.nhindirect;

import org.apache.log4j.Logger;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.openhealthtools.ihe.common.hl7v2.CX;
import org.openhealthtools.ihe.common.hl7v2.Hl7v2Factory;
import org.openhealthtools.ihe.common.hl7v2.XCN;
import org.openhealthtools.ihe.common.hl7v2.XON;
import org.openhealthtools.ihe.xds.document.DocumentDescriptor;
import org.openhealthtools.ihe.xds.document.XDSDocument;
import org.openhealthtools.ihe.xds.document.XDSDocumentFromByteArray;
import org.openhealthtools.ihe.xds.document.XDSDocumentFromStream;
import org.openhealthtools.ihe.xds.metadata.*;
import org.openhealthtools.ihe.xds.metadata.extract.MetadataExtractionException;
import org.openhealthtools.ihe.xds.source.SubmitTransactionCompositionException;
import org.openhealthtools.ihe.xds.source.SubmitTransactionData;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.mail.internet.MimeMultipart;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.Random;

/**
 * Transforms E-Mail messages to XDS RepositorySubmissions for use with XDR/XDM operations.  This demonstration
 * code was developed for NHINDirect.org as a proof of concept for 'stepping up' from MIME mail to IHE XDR.
 * The general idea is to convert the MIME message to XDR using as much structured information as possible
 * in the message.  For this demonstration only HL7 CDA Release 2 document attachements are supported for
 * structured data in email, but production implementations could extract data from other attachement types
 * such as XDM archive, CCR, PDF/H, DICOM, etc.  Minimally speaking, a plain/text email could be 'stepped up' to XDR using
 * default classification codes, and surrogate patient/document identifiers.  This code is meant to
 * illustrate to the NHINDirect community the basic idea.
 * <p>
 * <pre>
 * Mapping from MIME to XD* metadata:
 *
 * MIME Attribute         XD* Attribute
 * ---------------------  ---------------------------------------------------------------------------------
 * To                     SubmissionSet.intendedRecipient (XCN person)
 * From                   SubmissionSet.authorPerson
 * Message-ID             When MIMEMessage has no CDA attachment, used to generate SubmissionSet.patientId,
 *                        SubmissionSet.sourcePatientId, DocumentEntry.patientId and DocumentEntry.sourcePatientId
 * Date                   SubmissionSet.submissionTime
 * Organization           SubmissionSet.authorInstitution
 * X-Organization         SubmissionSet.authorInstitution
 * Subject                SubmissionSet.title
 *
 * Each MIME Body Part:
 * Content-Type           XDSDocumentEntry.mimeType
 * Filename               for unstructured documents XDSDocumentEntry.title
 * Content                XDSDocumentEntry.formatCode (by inspecting content)
 *
 *
 * </pre>
 */
public class XDSMimeMessageTransformer {

    private static final Logger LOGGER = Logger.getLogger(XDSMimeMessageTransformer.class);

    static final String MIMETYPE_TEXT_PLAIN = "text/plain";
    static final String MIMETYPE_TEXT_XML = "text/xml";
    static final String MIMETYPE_CDA = "text/cda+xml";
    static final String MIMETYPE_CCR = "application/ccr";

    static final String DEFAULT_LANGUAGE_CODE = "en-us";   // TODO default from Locale
    static final String RANDOM_OID_ROOT = "1.3.6.1.4.1.21367.3100.1.2.3";
    static final String NHINDIRECT_MESSAGE_ID_ASSIGNING_AUTHORITY_NAME = "NHINDirect";
    static final String NHINDIRECT_ADDR_OID = "1.3.6.1.4.1.21367.3100.1";
    static final String NHINDIRECT_MESSAGE_ID_ASSIGNING_AUTHORITY_ID = "1.3.6.1.4.1.21367.3100.1.1";
    static final String NHINDIRECT_MESSAGE_METADATA_CODESYSTEM_ID = "1.3.6.1.4.1.21367.3100.1.2";

    static final CodedMetadataType CODE_UNSPECIFIED = createCode("Unspecified", NHINDIRECT_MESSAGE_METADATA_CODESYSTEM_ID, "Unspecified document classification code");
    static final CodedMetadataType CODE_CLINICALDATA = createCode("Clinical Data", NHINDIRECT_MESSAGE_METADATA_CODESYSTEM_ID, "Unspecified clinical data transferred via NHIN Direct");
    static final CodedMetadataType CODE_CONTENT_TYPE = createCode("Communication", "Connect-a-thon contentTypeCodes", "Communication");
    static final CodedMetadataType CODE_CONFIDENTIALITY = createCode("N", "Connect-a-thon confidentialityCodes", "Normal");
    static final CodedMetadataType CODE_FORMAT_TEXT = createCode("TEXT", "Connect-a-thon formatCodes", "TEXT");
    static final CodedMetadataType CODE_FORMAT_CDAR2 = createCode("CDAR2/IHE 1.0", "Connect-a-thon formatCodes", "CDAR2/IHE 1.0");
    static final CodedMetadataType CODE_FORMAT_CCRV1 = createCode("CCR V1.0", "Connect-a-thon formatCodes", "CCR V1.0");
    static final CodedMetadataType CODE_FORMAT_PDF = createCode("PDF", "Connect-a-thon formatCodes", "PDF");

    /**
     * Converted metadata will comply with minimum XDS metadata requirements.  If the message contains structured
     * documents such as CDA, XDSDocumentEntry metadata will be constructed as much as possible from the
     * CDA header.  Where minimum XDS metadata requirements cannot be met from MIME part metadata the XDS metadata
     * will be enriched with generic defaults.
     * <p/>
     * For multipart MIME all message parts are encoded with each part represented as an XDS document entry.
     * <p/>
     * For a simple text message the message body is represented as a single XDS Document with enriched XDS
     * metadata to supply generic defaults for required XDS classifiers and psuedonymized identifiers.
     */
    static public SubmitTransactionData convert(MimeMessage mimeMessage) throws MessagingException, IOException {

        SubmitTransactionData txnData = new SubmitTransactionData();

        String msgContentType = mimeMessage.getContentType();

        if (msgContentType.startsWith("multipart/mixed")) {

            MimeMultipart mimeMultipart = (MimeMultipart) mimeMessage.getContent();

            // we grab any CDA document attachments and add them to the submission using OHT
            for (int partIndex = 0; partIndex < mimeMultipart.getCount(); partIndex++) {

                BodyPart bodyPart = mimeMultipart.getBodyPart(partIndex);
                String contentType = bodyPart.getContentType();
                String xdsMimeType = null;
                CodedMetadataType xdsFormatCode = null;
                XDSDocument xdsDocument = null;

                if (bodyPart.getSize() > 0) {

                    InputStream is = bodyPart.getInputStream();
                    ByteArrayOutputStream baos = new ByteArrayOutputStream();
                    byte[] buf = new byte[1024];
                    int i = 0;
                    while ((i = is.read(buf)) != -1) {
                        baos.write(buf, 0, i);
                    }
                    String contentString = new String(baos.toByteArray());

                    try {
                        // special handling for recognized content types
                        if (contentType.startsWith(MIMETYPE_TEXT_PLAIN)) {
                            if (contentString.trim().length() == 0) {
                                continue;  // skip 'empty' parts
                            }
                            xdsDocument = new XDSDocumentFromByteArray(DocumentDescriptor.MIME_TYPE_MAP.get(MIMETYPE_TEXT_PLAIN), baos.toByteArray());
                            xdsMimeType = MIMETYPE_TEXT_PLAIN;
                            xdsFormatCode = CODE_FORMAT_TEXT;
                        } else if (contentType.startsWith(MIMETYPE_TEXT_XML)) {

                            // its a CDA R2
                            if (contentString.contains("urn:hl7-org:v3") && contentString.contains("POCD_HD000040")) {
                                xdsDocument = new XDSDocumentFromByteArray(DocumentDescriptor.CDA_R2, baos.toByteArray());
                                xdsMimeType = MIMETYPE_TEXT_XML;
                                xdsFormatCode = CODE_FORMAT_CDAR2;
                            }

                            // its some other XML (maybe a CCR or HL7 message)  TODO support more XML types
                            else {
                                xdsDocument = new XDSDocumentFromStream(DocumentDescriptor.MIME_TYPE_MAP.get(MIMETYPE_TEXT_XML), is);
                                xdsMimeType = MIMETYPE_TEXT_XML;
                                xdsFormatCode = CODE_FORMAT_TEXT;
                            }

                            // otherwise make best effort passing MIME content type thru
                        } else {
                            xdsMimeType = contentType;
                            xdsFormatCode = CODE_FORMAT_TEXT;
                        }

                        try {
                            String documentUUID = txnData.addDocument(xdsDocument);
                            DocumentEntryType xdsDocumentEntry = txnData.getDocumentEntry(documentUUID);
                            xdsDocumentEntry.setMimeType(xdsMimeType);
                            xdsDocumentEntry.setFormatCode(xdsFormatCode);
                            if (xdsDocumentEntry.getTitle() == null && bodyPart.getFileName() != null) {
                                xdsDocumentEntry.setTitle(createIntlString(bodyPart.getFileName()));
                            }

                        } catch (MetadataExtractionException e) {
                            LOGGER.warn("OHT metadata extraction failed, metadata defaults will be used", e);
                        } catch (SubmitTransactionCompositionException e) {
                            throw new RuntimeException(new MessagingException("ERROR adding document to submission: ", e));
                        }

                    } finally {
                        is.close();
                    }
                }
            }

            enrichMetadata(mimeMessage, txnData);

            // support for plain mail, no attachments
        } else if (msgContentType.startsWith(MIMETYPE_TEXT_PLAIN)) {

            XDSDocument xdsDocument =
                    new XDSDocumentFromByteArray(DocumentDescriptor.MIME_TYPE_MAP.get(MIMETYPE_TEXT_PLAIN), ((String) mimeMessage.getContent()).getBytes());

            try {
                String documentUUID = txnData.addDocument(xdsDocument);
                DocumentEntryType xdsDocumentEntry = txnData.getDocumentEntry(documentUUID);
                xdsDocumentEntry.setMimeType(MIMETYPE_TEXT_PLAIN);
                if (mimeMessage.getSubject() != null) {
                    xdsDocumentEntry.setTitle(createIntlString(mimeMessage.getSubject()));
                }
            } catch (MetadataExtractionException e) {
                LOGGER.warn("OHT metadata extraction failed, metadata defaults will be used", e);
            } catch (SubmitTransactionCompositionException e) {
                throw new RuntimeException(new MessagingException("ERROR adding document to submission: ", e));
            }

            enrichMetadata(mimeMessage, txnData);

        } else {
            throw new MessagingException("message content type " + msgContentType + " is not supported");
        }

        return txnData;
    }

    /**
     * Enriches the RepositorySubmission transaction with MIME/Mail metadata appropriate for XDR
     */
    static private void enrichMetadata(MimeMessage mimeMessage, SubmitTransactionData txnData) {

        // enrich all document metadata
        for (XDSDocument xdsDocument : txnData.getDocList()) {
            enrichXDSDocumentEntry(mimeMessage, txnData.getDocumentEntry(xdsDocument.getDocumentEntryUUID()));
        }
        enrichXDSSubmissionSet(mimeMessage, txnData);

    }

    /**
     * Enrich the SubmissionSet metadata in the specified RepositorySubmission transaction data using MIME
     * message metadata and other NHINDirect defaults to ensure minimal XDS metadata requirements are met.
     */
    static private void enrichXDSSubmissionSet(MimeMessage mimeMessage, SubmitTransactionData txnData) {

        SubmissionSetType subSet = txnData.getSubmissionSet();

        // apply a default content type, if none exists
        if (subSet.getContentTypeCode() == null) {
            subSet.setContentTypeCode((CodedMetadataType) EcoreUtil.copy(CODE_CONTENT_TYPE));
        }

        // apply an author from MIME message, if none exists
        if (subSet.getAuthor() == null) {
            subSet.setAuthor(createAuthor(mimeMessage));
        }

        subSet.setUniqueId(createUniqueId());  // TODO this could be mapped to messageId at gateway to enable traceability
        subSet.setSubmissionTime(createTimestamp(mimeMessage));

        try {
            if (subSet.getTitle() == null && mimeMessage.getSubject() != null) {
                subSet.setTitle(createIntlString(mimeMessage.getSubject()));
            }
        } catch (MessagingException e) {
            LOGGER.warn("failure transforming MIME subject header to XDSSubmissionSet.title");
        }

        // apply patientId from first document to submissionSet
        if (subSet.getPatientId() == null) {
            String firstDocumentUUID = txnData.getDocList().get(0).getDocumentEntryUUID();
            DocumentEntryType xdsDocumentEntry = txnData.getDocumentEntry(firstDocumentUUID);
            subSet.setPatientId((CX) EcoreUtil.copy(xdsDocumentEntry.getPatientId()));
        }

        enrichSubmissionWithNHINDirectAddress(mimeMessage, txnData);
    }

    /**
     * Applies the specified NHINDirect address to the XDS metadata in accordance with
     * NHINDirect IHE Concrete Implementation WG specification.
     */
    static private void enrichSubmissionWithNHINDirectAddress(MimeMessage mimeMessage, SubmitTransactionData txnData) {

        try {
            for (Address address : mimeMessage.getRecipients(Message.RecipientType.TO)) {
                XCN xcn = createXCN(address);
                if (xcn != null) {
                    IntendedRecipientType intendedRecipient = MetadataFactory.eINSTANCE.createIntendedRecipientType();
                    intendedRecipient.setPerson(createXCN(address));
                    txnData.getSubmissionSet().getIntendedRecipient().add(intendedRecipient);
                }
            }
        } catch (MessagingException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Creates an HL7 XCN instance from a MIME internet address.
     */
    static XCN createXCN(Address address) {
        XCN xcn = null;
        if (address instanceof InternetAddress) {
            InternetAddress recipientAddress = (InternetAddress) address;
            String nhinDirectAddress = recipientAddress.getAddress();
            xcn = Hl7v2Factory.eINSTANCE.createXCN();
            xcn.setAssigningAuthorityName("NHIN-Direct health address");
            xcn.setAssigningAuthorityUniversalId(NHINDIRECT_ADDR_OID);
            xcn.setAssigningAuthorityUniversalIdType("ISO");
            xcn.setIdNumber(nhinDirectAddress);
        }
        return xcn;
    }

    static void enrichXDSDocumentEntry(MimeMessage mimeMessage, DocumentEntryType xdsDocumentEntry) {

        String title = null;
        String comment = null;

        // stuff in message time
        if (xdsDocumentEntry.getCreationTime() == null) {
            xdsDocumentEntry.setCreationTime(createTimestamp(mimeMessage));
        }

        // use message id as sourcePatientId if not derivable from content
        if (xdsDocumentEntry.getSourcePatientId() == null) {
            xdsDocumentEntry.setSourcePatientId(createCXFromMessageId(mimeMessage));
        }

        // for XDR/XDM we just use the sourcePatientId as the patientId
        if (xdsDocumentEntry.getPatientId() == null) {
            xdsDocumentEntry.setPatientId((CX) EcoreUtil.copy(xdsDocumentEntry.getSourcePatientId()));
        }

        if (title != null) {
            xdsDocumentEntry.setTitle(createIntlString(title));
        }
        if (comment != null) {
            xdsDocumentEntry.setComments(createIntlString(comment));
        }

        if (xdsDocumentEntry.getUniqueId() == null) {
            xdsDocumentEntry.setUniqueId(createUniqueId());
        }

        if (xdsDocumentEntry.getConfidentialityCode().size() > 0) {
            if (!isCodeValid((CodedMetadataType) xdsDocumentEntry.getConfidentialityCode().get(0))) {
                xdsDocumentEntry.getConfidentialityCode().clear();
                xdsDocumentEntry.getConfidentialityCode().add(EcoreUtil.copy(CODE_CONFIDENTIALITY));
            }
        } else {
            xdsDocumentEntry.getConfidentialityCode().add(EcoreUtil.copy(CODE_CONFIDENTIALITY));
        }

        if (!isCodeValid(xdsDocumentEntry.getFormatCode())) {
            xdsDocumentEntry.setFormatCode((CodedMetadataType) EcoreUtil.copy(CODE_UNSPECIFIED));
        }
        if (!isCodeValid(xdsDocumentEntry.getHealthCareFacilityTypeCode())) {
            xdsDocumentEntry.setHealthCareFacilityTypeCode((CodedMetadataType) EcoreUtil.copy(CODE_UNSPECIFIED));
        }
        if (!isCodeValid(xdsDocumentEntry.getPracticeSettingCode())) {
            xdsDocumentEntry.setPracticeSettingCode((CodedMetadataType) EcoreUtil.copy(CODE_UNSPECIFIED));
        }
        if (!isCodeValid(xdsDocumentEntry.getClassCode())) {
            xdsDocumentEntry.setClassCode((CodedMetadataType) EcoreUtil.copy(CODE_CLINICALDATA));
        }
        if (!isCodeValid(xdsDocumentEntry.getTypeCode())) {
            xdsDocumentEntry.setTypeCode((CodedMetadataType) EcoreUtil.copy(CODE_CLINICALDATA));
        }

        if (xdsDocumentEntry.getLanguageCode() == null) {
            xdsDocumentEntry.setLanguageCode(DEFAULT_LANGUAGE_CODE);
        }

        if (xdsDocumentEntry.getAuthor() != null) {
            XCN authorPerson = xdsDocumentEntry.getAuthor().getAuthorPerson();
            if (authorPerson != null) {
                if (authorPerson.getFamilyName() == null &&
                        authorPerson.getIdNumber() == null) {
                    // CDA parsed record is insufficient, delete it
                    xdsDocumentEntry.getAuthor().setAuthorPerson(null);
                }
            }
        }
    }

    static private String createTimestamp(Date date) {
        SimpleDateFormat formatter = new SimpleDateFormat("yyyyMMddHHmmss");
        return formatter.format(date);
    }

    static private CX createCXFromMessageId(MimeMessage mimeMessage) {
        CX cx = Hl7v2Factory.eINSTANCE.createCX();
        cx.setAssigningAuthorityName(NHINDIRECT_MESSAGE_ID_ASSIGNING_AUTHORITY_NAME);
        cx.setAssigningAuthorityUniversalId(NHINDIRECT_MESSAGE_ID_ASSIGNING_AUTHORITY_ID);
        cx.setAssigningAuthorityUniversalIdType("ISO");
        try {
            cx.setIdNumber(mimeMessage.getMessageID());
        } catch (MessagingException e) {
            LOGGER.error("failed to extract HL7 CX from MIME Message-ID header", e);
            throw new RuntimeException(e);   // TODO blows up spectacularly, could generate a random surrogate id when no Message-ID header is present
        }
        return cx;
    }

    static private CodedMetadataType createCode(String codeValue, String codingScheme, String displayName) {
        CodedMetadataType code = MetadataFactory.eINSTANCE.createCodedMetadataType();
        code.setCode(codeValue);
        code.setSchemeName(codingScheme);
        code.setDisplayName(createIntlString(displayName));
        return code;
    }

    static private InternationalStringType createIntlString(String str) {
        LocalizedStringType strLoc = MetadataFactory.eINSTANCE.createLocalizedStringType();
        strLoc.setLang("en-US");
        strLoc.setValue(str);
        InternationalStringType strIntl = MetadataFactory.eINSTANCE.createInternationalStringType();
        strIntl.getLocalizedString().add(strLoc);
        return strIntl;
    }

    /**
     * Given a MimeMessage attempts to create a well formed XDS Submission Time timestamp string
     * from the MimeMessage Message Date/Time.  If this fails a timestamp based on current system
     * time is generated.
     *
     * @param mimeMessage
     * @return
     */
    static private String createTimestamp(MimeMessage mimeMessage) {

        String timestamp = null;

        try {
            timestamp = createTimestamp(mimeMessage.getSentDate());
        } catch (MessagingException e) {
            LOGGER.warn("failed extracting XDS timestamp from MIME SentDate", e);
            timestamp = createTimestamp(new Date());
        }
        return timestamp;
    }

    /**
     * Given a MimeMessage construct a proper set of XDS Author metadata for use in populating XDSDocumentEntry
     * or XDSSubmissionSet metadata.
     *
     * @param mimeMessage
     * @return
     */
    static private AuthorType createAuthor(MimeMessage mimeMessage) {

        AuthorType xdsAuthor = MetadataFactory.eINSTANCE.createAuthorType();

        try {
            Enumeration<Header> orgHeaders = mimeMessage.getMatchingHeaders(new String[]{"Organization", "Organisation"});
            while (orgHeaders.hasMoreElements()) {
                Header header = orgHeaders.nextElement();
                if (header.getValue() != null && header.getValue().trim().length() > 0) {
                    XON xon = Hl7v2Factory.eINSTANCE.createXON();
                    xon.setOrganizationName(header.getValue());
                    xdsAuthor.getAuthorInstitution().add(xon);
                }
            }

            // only expects a single From
            for (Address fromAddress : mimeMessage.getFrom()) {
                xdsAuthor.setAuthorPerson(createXCN(fromAddress));
            }

        } catch (MessagingException e) {
            LOGGER.warn("failed extracting XDS author information from MIME message", e);
        }

        return xdsAuthor;
    }

    static private AuthorType createAuthor(AuthorType
            author) {
        AuthorType newAuthor = MetadataFactory.eINSTANCE.createAuthorType();
        newAuthor.setAuthorPerson(createXCN(author.getAuthorPerson()));
        EList<XON> authorInstitutionList = author.getAuthorInstitution();
        if (authorInstitutionList.size() > 0) {
            EList<XON> newAuthorInstitutionList = newAuthor.getAuthorInstitution();
            for (XON xon : authorInstitutionList) {
                newAuthorInstitutionList.add(createXON(xon));
            }
        }
        EList<String> authorRoleList = author.getAuthorRole();
        if (authorRoleList.size() > 0) {
            EList<String> newAuthorRoleList = newAuthor.getAuthorRole();
            for (String role : authorRoleList) {
                newAuthorRoleList.add(role);
            }
        }
        EList<String> authorSpecialityList = author.getAuthorSpeciality();
        if (authorSpecialityList.size() > 0) {
            EList<String> newAuthorSpecialtyList = newAuthor.getAuthorSpeciality();
            for (String specialty : authorSpecialityList) {
                newAuthorSpecialtyList.add(specialty);
            }
        }
        return newAuthor;
    }

    static private XON createXON(XON
            xon) {
        XON newXON = Hl7v2Factory.eINSTANCE.createXON();
        newXON.setAssigningAuthorityName(xon.getAssigningAuthorityName());
        newXON.setAssigningAuthorityUniversalId(xon.getAssigningAuthorityUniversalId());
        newXON.setAssigningAuthorityUniversalIdType(xon.getAssigningAuthorityUniversalIdType());
        newXON.setIdNumber(xon.getIdNumber());
        newXON.setOrganizationName(xon.getOrganizationName());
        return newXON;
    }

    static private XCN createXCN(XCN
            xcn) {
        XCN newXCN = Hl7v2Factory.eINSTANCE.createXCN();
        newXCN.setAssigningAuthorityName(xcn.getAssigningAuthorityName());
        newXCN.setAssigningAuthorityUniversalId(xcn.getAssigningAuthorityUniversalId());
        newXCN.setAssigningAuthorityUniversalIdType(xcn.getAssigningAuthorityUniversalIdType());
        newXCN.setFamilyName(xcn.getFamilyName());
        newXCN.setGivenName(xcn.getGivenName());
        newXCN.setIdNumber(xcn.getIdNumber());
        newXCN.setOtherName(xcn.getOtherName());
        newXCN.setPrefix(xcn.getPrefix());
        newXCN.setSuffix(xcn.getSuffix());
        return newXCN;
    }

    static private CX createPatientId(String idValue, String idDomainOID) {
        CX patientId = Hl7v2Factory.eINSTANCE.createCX();
        patientId.setIdNumber(idValue);
        patientId.setAssigningAuthorityUniversalId(idDomainOID);
        patientId.setAssigningAuthorityUniversalIdType("ISO");
        return patientId;
    }

    static private boolean isCodeValid(CodedMetadataType code) {
        return (code != null && code.getCode() != null && code.getSchemeName() != null);
    }

    static Random random = new Random(System.currentTimeMillis());

    static private String createUniqueId() {
        return RANDOM_OID_ROOT + "." + System.currentTimeMillis() + "." + Math.abs(random.nextInt()) % 100000;
    }

    /**
     * Applies the specified patient identifier to the submission envelope and contents.  If the override
     * flag is true, then any existing value for patientId will be replaced, if false, only XDS objects with
     * a null patientId will be updated.
     */
    static private void applyExternalPatientIdToEntireSubmission(SubmitTransactionData txnData, CX
            pid, boolean overwrite) {
        if (txnData.getSubmissionSet().getPatientId() == null || overwrite) {
            txnData.getSubmissionSet().setPatientId(createPatientId(pid.getIdNumber(), pid.getAssigningAuthorityUniversalId()));
        }
        List docList = txnData.getDocList();
        for (Object obj : docList) {
            XDSDocument doc = (XDSDocument) obj;
            DocumentEntryType docEntry = txnData.getDocumentEntry(doc.getDocumentEntryUUID());
            if (docEntry.getPatientId() == null || overwrite) {
                docEntry.setPatientId(createPatientId(pid.getIdNumber(), pid.getAssigningAuthorityUniversalId()));
            }
        }
    }
}
